﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using QTPlugin;
using Kawanon.QtHoney;

namespace Kawanon.Usability
{
	public class GestureProvider
	{
		private const uint WM_LBUTTONDOWN = 0x0201;
		private const uint WM_LBUTTONUP = 0x0202;
		private const uint WM_RBUTTONDOWN = 0x0204;
		private const uint WM_RBUTTONUP = 0x0205;
		private const uint WM_MBUTTONDOWN = 0x0207;
		private const uint WM_MBUTTONUP = 0x0208;
		private const uint WM_XBUTTONDOWN = 0x020B;
		private const uint WM_XBUTTONUP = 0x020C;
		private const uint WM_MOUSEMOVE = 0x0200;
		private const uint WM_CAPTURECHANGED = 0x0215;
		private const uint WM_CONTEXTMENU = 0x007B;
		private const uint WM_TIMER = 0x0113;
		private const uint WM_CLOSE = 0x0010;

		private const uint XBUTTON1 = 0x0001;
		private const uint XBUTTON2 = 0x0002;

		[DllImport("user32.dll")]
		static extern bool GetAsyncKeyState(System.Windows.Forms.Keys vKey);

		//[DllImport("user32.dll", ExactSpelling = true)]
		//static extern IntPtr SetTimer(IntPtr hWnd, IntPtr nIDEvent, uint uElapse, TimerProc lpTimerFunc);
		//delegate void TimerProc(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime);

		//[DllImport("user32.dll", ExactSpelling = true)]
		//[return: MarshalAs(UnmanagedType.Bool)]
		//static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent);

		private const int D_NORTH = 0;
		private const int D_EAST = 1;
		private const int D_SOUTH = 2;
		private const int D_WEST = 3;

		//private const int DEFAULT_MIN_DISTANCE = 15;
		private const int MAX_DEFER_TICKS = 1000 * 10000;
		private const int DEFER_PIXEL = 2;

		public class NotifyEventArgs : EventArgs
		{
			public Point Previous { get; set; }
			public Point Current { get; set; }
		}

		public delegate void NotifyEventHandler(object sender, NotifyEventArgs e);

		private struct GestureInput
		{
			public int Distance;
			public bool Ignored;
		}

		private bool m_inCasting = false;
		private Point m_previousPos;
		private GestureInput[] m_inputs;	// = new GestureInput[4];
		//NOTE: VistaではGesturePanel上でジェスチャを入力中にフリーズすることがあるので、代わりに Timer を使う
		//private IntPtr m_timerId = IntPtr.Zero;
		private Timer m_timer = new Timer();
		private int m_trailStage = 0;
		private List<GestureExpression> m_suggestions = new List<GestureExpression>();
		private bool m_bCancelContextMenu = false;
		private List<int> m_trails = new List<int>();
		private Point m_originPos;
		private long m_deferedTicks;
		private object m_target;
		private Keys m_button;

		public IEnumerable<GestureExpression> Expressions { get; set; }
		public bool AllowUnknownExpression { get; set; }
		public GestureInformation GestureInformation { get; set; }

		public GestureProvider()
		{
			//m_exprs.Add(new GestureExpression(new int[] { D_WEST }, GestureAction.From(Commands.GoBack, 1)));
			//m_exprs.Add(new GestureExpression(new int[] { D_EAST }, GestureAction.From(Commands.GoForward, 1)));
			//m_exprs.Add(new GestureExpression(new int[] { D_NORTH, D_WEST }, GestureAction.From(Commands.UndoClose)));
			//m_exprs.Add(new GestureExpression(new int[] { D_SOUTH, D_EAST }, GestureAction.From(Commands.ToggleTopMost)));
			// 無効な設定は省く
			//m_exprs.AddRange(GestureSettings.GetDefaultExpressions());

			//MinimumDistance = DEFAULT_MIN_DISTANCE;

			m_timer.Tick += new EventHandler(Timer_Tick);
			m_timer.Interval = 100;
		}

		~GestureProvider()
		{
			EndGesture();
		}

		public event GestureEventHandler GestureDetected;
		public event GestureEventHandler TrailDetected;
		public event MouseEventHandler RightClick;
		public event GestureEventHandler ResolveTarget;
		public event NotifyEventHandler Tick;
		public event GestureEventHandler GestureCancelled;

		private GestureExpression CreateCurrentExpression(Action action)
		{
			return new GestureExpression(m_trails.ToArray(), action, m_button);
		}

		private void TrackGestureExpression(int direction)
		{
			//if (m_trailStage == 0)
			//{
			//    return;
			//}
			for (int i = m_suggestions.Count - 1; i >= 0; i--)
			{
				GestureExpression expr = m_suggestions[i];
				if (m_button != expr.Button ||
					expr.Trails.Length < m_trailStage ||
					expr.Trails[m_trailStage - 1] != direction)
				{
					m_suggestions.RemoveAt(i);
				}
			}
		}

		private GestureExpression TrackGestureExpression()
		{
			if (m_trailStage == 0)
			{
				return null;
			}
			for (int i = m_suggestions.Count - 1; i >= 0; i--)
			{
				GestureExpression expr = m_suggestions[i];
				if (expr.Trails.Length != m_trailStage)	// 軌跡数が一致しない
				{
					m_suggestions.RemoveAt(i);
				}
			}
			if (m_suggestions.Count == 1)
			{
				return m_suggestions[0];
			}
			else if (m_suggestions.Count == 0)
			{
				return null;
			}
			else
			{
				Dbg.Abort("TrackGestureExpression: Duplication");
				return null;
			}
		}

		private void ResetGesture()
		{
			//for (int i = 0; i < m_inputs.Length; i++)
			//{
			//    m_inputs[i] = new GestureInput();
			//}
			m_inputs = new GestureInput[4];
		}

		private void RecognizeGesture(int direction)
		{
			GestureInput input = m_inputs[direction];
			if (!input.Ignored && input.Distance >= GestureInformation.MinimumDistance)
			{
				ResetGesture();

				//Console.WriteLine(direction);

				m_inputs[direction].Ignored = true;	// 構造体なのでinputは無意味
				m_trails.Add(direction);
				m_trailStage++;

				TrackGestureExpression(direction);

				if (TrailDetected != null)
				{
					GestureEventArgs e = new GestureEventArgs(
						CreateCurrentExpression(new Usability.Actions.Pending()),
						m_suggestions.AsReadOnly());
					e.Target = m_target;

					TrailDetected.Invoke(this, e);
				}
			}
		}

		private void ProcessGesture(Point pos)
		{
			int disX = m_previousPos.X - pos.X;
			int disY = m_previousPos.Y - pos.Y;

			m_previousPos = pos;

			if (Math.Abs(disX) > Math.Abs(disY))
			{
				if (disX > 0)
				{
					m_inputs[D_WEST].Distance += disX;
					m_inputs[D_EAST].Distance = 0;

					RecognizeGesture(D_WEST);
				}
				else if (disX < 0)
				{
					m_inputs[D_EAST].Distance += -disX;
					m_inputs[D_WEST].Distance = 0;

					RecognizeGesture(D_EAST);
				}
			}
			else
			{
				if (disY > 0)
				{
					m_inputs[D_NORTH].Distance += disY;
					m_inputs[D_SOUTH].Distance = 0;

					RecognizeGesture(D_NORTH);
				}
				else
				{
					m_inputs[D_SOUTH].Distance += -disY;
					m_inputs[D_NORTH].Distance = 0;

					RecognizeGesture(D_SOUTH);
				}
			}
		}

		private void RaiseGestureDetected(GestureExpression expr)
		{
			if (expr != null)
			{
				if (GestureDetected != null)
				{
					GestureEventArgs e = new GestureEventArgs(expr);
					e.Target = m_target;
					GestureDetected.Invoke(this, e);
				}

				m_bCancelContextMenu = true;	// コマンドを実行したので、メニューはキャンセル
			}
			else if (AllowUnknownExpression)
			{
				if (GestureDetected != null)
				{
					GestureEventArgs e = new GestureEventArgs(
						CreateCurrentExpression(new Usability.Actions.Unknown()));
					e.Target = m_target;
					GestureDetected.Invoke(this, e);
				}

				m_bCancelContextMenu = true;
			}
		}

		private void EndGesture()
		{
			if (!m_inCasting)
			{
				return;
			}

			m_inCasting = false;

			if (m_trailStage > 0)
			{
				GestureExpression expr = TrackGestureExpression();
				RaiseGestureDetected(expr);
			}
			else
			{
				if (GestureCancelled != null)
				{
					GestureCancelled.Invoke(this, new GestureEventArgs(
						CreateCurrentExpression(new Actions.Unknown())));
				}
			}

			m_trailStage = 0;
			m_button = Keys.None;

			m_timer.Stop();
		}

		void Timer_Tick(object sender, EventArgs e)
		{
			//Console.WriteLine("{0} {1} {2}", hWnd, uMsg, nIDEvent);

			if (m_inCasting)
			{
				// キャプチャはできないので、コントロール外に出たら無効に...
				if (GetAsyncKeyState(m_button))
				{
					Point pos = Cursor.Position;
					if (Tick != null)
					{
						NotifyEventArgs args = new NotifyEventArgs();
						args.Previous = m_previousPos;
						args.Current = pos;
						Tick.Invoke(this, args);
					}
					ProcessGesture(pos);
					return;
				}
			}

			EndGesture();
		}

		private void BeginGesture(Keys button)
		{
			m_button = button;
			m_inCasting = true;
			// タイマ割り込みでおかしなことになる？
			//m_timerId = SetTimer(IntPtr.Zero, IntPtr.Zero, 50, new TimerProc(GestureTimerProc));
			//if (m_timerId == IntPtr.Zero)
			//{
			//    Dbg.Abort("SetTimer");
			//}
			m_timer.Start();
			m_trails.Clear();
			m_suggestions.Clear();
			if (Expressions != null)
			{
				m_suggestions.AddRange(Expressions);
			}
			m_bCancelContextMenu = false;
			m_originPos = Cursor.Position;
			m_previousPos = m_originPos;

			ResetGesture();

			//NOTE: すぐにボタンが離された場合、右クリックをエミュレートさせる
			m_deferedTicks = DateTime.Now.Ticks;

			if (ResolveTarget != null)
			{
				GestureEventArgs e = new GestureEventArgs(
					CreateCurrentExpression(new Usability.Actions.Pending()));
				ResolveTarget.Invoke(this, e);
				//TODO: 2個以上のデリゲートが登録されていた場合は？
				m_target = e.Target;
			}
			else
			{
				m_target = null;
			}
		}

		private bool MaybeRightClick()
		{
			if (m_deferedTicks != 0 && DateTime.Now.Ticks - m_deferedTicks <= MAX_DEFER_TICKS)
			{
				Point pt = Cursor.Position;
				pt.Offset(-DEFER_PIXEL, -DEFER_PIXEL);
				Rectangle rc = new Rectangle(pt, new Size(DEFER_PIXEL * 2 + 1, DEFER_PIXEL * 2 + 1));
				if (rc.Contains(m_originPos))
				{
					if (RightClick != null)
					{
						MouseEventArgs e = new MouseEventArgs(MouseButtons.Right, 1, m_originPos.X, m_originPos.Y, 0);
						RightClick.Invoke(this, e);
					}
					return true;
				}
				m_deferedTicks = 0;
			}
			return false;
		}

		public bool ProcessMessage(ref Message m)
		{
			switch ((uint)m.Msg)
			{
				case WM_RBUTTONDOWN:
				case WM_MBUTTONDOWN:
				case WM_XBUTTONDOWN:
					if (m_inCasting)
					{
						break;	//レアケース
					}
					if (m.Msg == WM_RBUTTONDOWN)
					{
						BeginGesture(Keys.RButton);
					}
					else if (m.Msg == WM_MBUTTONDOWN)
					{
						BeginGesture(Keys.MButton);
					}
					else if (((uint)m.WParam >> 16) == XBUTTON1)
					{
						BeginGesture(Keys.XButton1);
					}
					else
					{
						BeginGesture(Keys.XButton2);
					}
					return true;

				case WM_RBUTTONUP:
					EndGesture();

					if (MaybeRightClick())
					{
						return true;
					}
					break;

				case WM_MBUTTONUP:
				case WM_XBUTTONUP:
					EndGesture();
					break;

				case WM_CONTEXTMENU:
					if (m_inCasting && m_trailStage > 0)
					{
						EndGesture();
						return true;	// 右クリックメニューを出さない
					}
					if (m_bCancelContextMenu)
					{
						m_bCancelContextMenu = false;
						return true;
					}
					EndGesture();

					if (MaybeRightClick())
					{
						return true;
					}
					break;
			}
			return false;
		}

	}
}
